import { myBox2D } from "../chapter4/mybox2d";
import { GameBody, levelData_1 } from "../chapter4/gameData";
import * as Box2D from "box2d.ts";
export class Game {
  canvas: HTMLCanvasElement | null = null;
  context: CanvasRenderingContext2D | null = null;
  currentLevel: { number: number; hero: []; backgroundImage: HTMLImageElement | null; foregroundImage: HTMLImageElement | null } = {
    number: -1,
    hero: [],
    backgroundImage: null,
    foregroundImage: null,
  };
  slingshotImage: CanvasImageSource | null = null;
  slingshotFrontImage: CanvasImageSource | null = null;
  score: 0 | null = null;

  gameUI: {
    gamecanvas: boolean;
    scorescreen: boolean;
    gamestartscreen: boolean;
    levelselectscreen: boolean;
    loadingscreen: boolean;
    endingscreen: boolean;
  } = { gamecanvas: false, scorescreen: false, gamestartscreen: false, levelselectscreen: false, loadingscreen: false, endingscreen: false };

  mode = "intro";
  offsetLeft = 0;
  maxSpeed: number = 3;
  minOffset: number = 0;
  maxOffset: number = 300;

  slingshotX = 140;
  slingshotY = 280;
  slingshotBandX = 140 + 55;
  slingshotBandY = 280 + 23;
  ended = false;
  animationFrame = 0;

  box2d: myBox2D | null = null;
  gameBodyList: any[] = [];

  loader: any;
  test:number = 0

  constructor(option: { gameUI: { gamecanvas: boolean; scorescreen: boolean; gamestartscreen: boolean; levelselectscreen: boolean; loadingscreen: boolean; endingscreen: boolean }; canvas: any; context: CanvasRenderingContext2D }) {
    this.gameUI = option.gameUI;
    this.canvas = option.canvas;
    this.context = option.context;
    
  }

  init(game: Game) {
    this.box2d = new myBox2D({ canvas: this.canvas!, ctx: this.context! });
    this.box2d.init();
    levels.init(game);
    this.loader = loader.init(game);

    mouse.init(game);
    this.gameUI.gamecanvas = false;
    this.gameUI.scorescreen = false;
    this.gameUI.levelselectscreen = false;
    this.gameUI.loadingscreen = false;
    this.gameUI.endingscreen = false;
    this.gameUI.gamestartscreen = true;
  }

  start = () => {
    this.gameUI.loadingscreen = false;
    this.gameUI.endingscreen = false;
    this.gameUI.gamestartscreen = false;
    this.gameUI.gamecanvas = true;
    this.gameUI.scorescreen = true;
    this.gameUI.levelselectscreen = false;
    this.mode = "intro";
    this.offsetLeft = 0;
    this.ended = false;

    this.animationFrame = requestAnimationFrame(this.animate);
    // this.box2d?.animate();
  };

  panTo(newCenter: number) {
    this.minOffset = 0;
    this.maxOffset = this.currentLevel!.backgroundImage!.width - this.canvas!.width;

    // The current center of the screen is half the screen width from the left offset
    var currentCenter = this.offsetLeft + this.canvas!.width / 2;

    // If the distance between new center and current center is > 0 and we have not panned to the min and max offset limits, keep panning
    if (Math.abs(newCenter - currentCenter) > 0 && this.offsetLeft <= this.maxOffset && this.offsetLeft >= this.minOffset) {
      // We will travel half the distance from the newCenter to currentCenter in each tick
      // This will allow easing
      var deltaX = (newCenter - currentCenter) / 2;

      // However if deltaX is really high, the screen will pan too fast, so if it is greater than maxSpeed
      if (Math.abs(deltaX) > this.maxSpeed) {
        // Limit delta x to game.maxSpeed (and keep the sign of deltaX)
        deltaX = this.maxSpeed * Math.sign(deltaX);
      }

      // And if we have almost reached the goal, just get to the ending in this turn
      if (Math.abs(deltaX) <= 1) {
        deltaX = newCenter - currentCenter;
      }

      // Finally add the adjusted deltaX to offsetX so we move the screen by deltaX
      this.offsetLeft += deltaX;

      // And make sure we don't cross the minimum or maximum limits
      if (this.offsetLeft <= this.minOffset) {
        this.offsetLeft = this.minOffset;

        // Let calling function know that we have panned as close as possible to the newCenter
        return true;
      } else if (this.offsetLeft >= this.maxOffset) {
        this.offsetLeft = this.maxOffset;

        // Let calling function know that we have panned as close as possible to the newCenter
        return true;
      }
    } else {
      // Let calling function know that we have panned as close as possible to the newCenter
      return true;
    }
  }

  animate = () => {
    // this.context!.clearRect(0, 0, this.canvas!.width, this.canvas!.height);
    this.handlePanning();
    this.context!.drawImage(this.currentLevel.backgroundImage!, this.offsetLeft / 4, 0, this.canvas!.width, this.canvas!.height, 0, 0, this.canvas!.width, this.canvas!.height);
    // Then draw the foreground image, offset by the entire offsetLeft distance
    this.context!.drawImage(this.currentLevel.foregroundImage!, this.offsetLeft, 0, this.canvas!.width, this.canvas!.height, 0, 0, this.canvas!.width, this.canvas!.height);

    // Draw the base of the slingshot, offset by the entire offsetLeft distance
    this.context!.drawImage(this.slingshotImage!, this.slingshotX - this.offsetLeft, this.slingshotY); 

    // Draw the front of the slingshot, offset by the entire offsetLeft distance
    this.context!.drawImage(this.slingshotFrontImage!, this.slingshotX - this.offsetLeft, this.slingshotY);

    if (!this.ended) {
      this.animationFrame = requestAnimationFrame(this.animate);
    }

    this.box2d?.world?.Step(1 / 60, 8, 3);
    let node = this.box2d?.world?.GetBodyList();
    // console.log(node)
    while (node) {
      let b = node;
      node = node.GetNext();
      let fixtrue = b?.GetFixtureList();
      let userdata = b.GetUserData();
      if (userdata.name == "polygon") {
        this.box2d?.renderPolygon(this.context!, b!.GetPosition(), b!.GetAngle(), fixtrue!,userdata,this.offsetLeft);
      }
      if (userdata.name == "circular") {
        this.box2d?.renderCircular(this.context!, b!.GetPosition(), b!.GetAngle(), fixtrue!);
      }
      if (userdata.name == "complex") {
        while (fixtrue) {
          let f = fixtrue;
          fixtrue = fixtrue.GetNext();
          this.box2d?.renderComplex(this.context!, b!.GetPosition(), b!.GetAngle(), f!);
        }
      }
    }

    // this.test++;
    // if (this.test > 100) {
    //   console.log(node)
    //   cancelAnimationFrame(this.animationFrame);
    // }
  };
  handlePanning() {
    if (this.mode === "intro") {
      if (this.panTo(700)) {
        this.mode = "load-next-hero";
      }
    }

    if (this.mode === "wait-for-firing") {
      if (mouse.dragging) {
        this.panTo(mouse.x + this.offsetLeft);
      } else {
        this.panTo(this.slingshotX);
      }
    }

    if (this.mode === "load-next-hero") {
      this.mode = "wait-for-firing";
    }

    if (this.mode === "firing") {
      // If the mouse button is down, allow the hero to be dragged around and aimed
      // If not, fire the hero into the air
    }

    if (this.mode === "fired") {
      // Pan to the location of the current hero as he flies
      //Wait till the hero stops moving or is out of bounds
    }

    if (this.mode === "level-success" || this.mode === "level-failure") {
      // First pan all the way back to the left
      // Then show the game as ended and show the ending screen
    }
  }
}

export let levels: {
  // Level data
  game: Game | null;
  data: [
    {
      // First level
      foreground: string;
      background: string;
      entities: [];
    },
    {
      // Second level
      foreground: string;
      background: string;
      entities: [];
    }
  ];
  init(game: Game): void;
  load(num: number): void;
};

levels = {
  game: null,
  data: [
    {
      // First level
      foreground: "desert-foreground",
      background: "clouds-background",
      entities: [],
    },
    {
      // Second level
      foreground: "desert-foreground",
      background: "clouds-background",
      entities: [],
    },
  ],
  init(game: Game) {
    this.game = game;
  },
  load(num: number) {
    this.game!.currentLevel!.number = num;
    this.game!.currentLevel!.hero = [];
    this.game!.score = 0;
    document.getElementById("score")!.innerHTML = "Score: " + this.game!.score;
    var level = levels.data[num];
    // Load the background, foreground, and slingshot images
    this.game!.currentLevel!.backgroundImage = loader.loadImage("/img/images/backgrounds/" + level.background + ".png");
    this.game!.currentLevel!.foregroundImage = loader.loadImage("/img/images/backgrounds/" + level.foreground + ".png");
    this.game!.slingshotImage = loader.loadImage("/img/images/slingshot.png");
    this.game!.slingshotFrontImage = loader.loadImage("/img/images/slingshot-front.png");

    levelData_1.forEach((item, index) => {
      let bodyData = new GameBody(levelData_1[index], this.game!);
      if (item.type=="block") {
        loader.loadImage("/img/images/entities/"+item.name+".png");
      }
      
      // this.gameBodyList.push(bodyData);
    });

    if (loader.loaded) {
      this.game!.start();
    } else {
      loader.onload = this.game!.start;
    }
  },
};

export var loader: {
  game: Game | null;
  loaded: boolean;
  loadedCount: 0; // Assets that have been loaded so far
  totalCount: 0; // Total number of assets that need loading
  soundFileExtn: string;
  onload: () => void | null;
  init(game: Game): void;
  loadImage(url: string): HTMLImageElement;
  loadSound(url: string): HTMLAudioElement;
  itemLoaded(ev: any): void;
};

loader = {
  game: null,
  loaded: true,
  loadedCount: 0, // Assets that have been loaded so far
  totalCount: 0, // Total number of assets that need loading
  soundFileExtn: ".ogg",
  onload: () => {},
  init(game: Game) {
    this.game = game;
    // check for sound support
    var mp3Support, oggSupport;
    var audio = document.createElement("audio");
    if (audio.canPlayType) {
      // Currently canPlayType() returns:  "", "maybe" or "probably"
      mp3Support = "" !== audio.canPlayType("audio/mpeg");
      oggSupport = "" !== audio.canPlayType('audio/ogg; codecs="vorbis"');
    } else {
      // The audio tag is not supported
      mp3Support = false;
      oggSupport = false;
    }
    // Check for ogg, then mp3, and finally set soundFileExtn to undefined
    this.soundFileExtn = oggSupport ? ".ogg" : mp3Support ? ".mp3" : "undefined";

    

    return loader;
  },
  loadImage(url: string) {
    this.totalCount++;
    this.loaded = false;
    this.game!.gameUI.loadingscreen = true;
    var image = new Image();
    image.addEventListener("load", this.itemLoaded, false);
    image.src = url;
    return image;
  },

  loadSound(url: string) {
    this.totalCount++;
    this.loaded = false;
    this.game!.gameUI.loadingscreen = true;
    var audio = new Audio();
    audio.addEventListener("canplaythrough", loader.itemLoaded, false);
    audio.src = url + loader.soundFileExtn;
    return audio;
  },

  itemLoaded: (ev: any) => {
    // Stop listening for event type (load or canplaythrough) for this item now that it has been loaded
    ev.target.removeEventListener(ev.type, loader.itemLoaded, false);

    loader.loadedCount++;

    document.getElementById("loadingmessage")!.innerHTML = "Loaded " + loader.loadedCount + " of " + loader.totalCount;

    if (loader.loadedCount === loader.totalCount) {
      // Loader has loaded completely..
      // Reset and clear the loader
      loader.loaded = true;
      loader.loadedCount = 0;
      loader.totalCount = 0;

      // Hide the loading screen
      loader.game!.gameUI.loadingscreen = false;
      loader.onload();
    }
  },
};

var mouse: {
  game: Game | null;
  x: number;
  y: number;
  down: boolean;
  dragging: boolean;
  init(game: Game): void;
  mousemovehandler: (ev: any) => void;
  mousedownhandler: (ev: any) => void;
  mouseuphandler: (ev: any) => void;
};

mouse = {
  game: null,
  x: 0,
  y: 0,
  down: false,
  dragging: false,

  init(game: Game) {
    this.game = game;
    var canvas = document.getElementById("gamecanvas");
    canvas!.addEventListener("mousemove", this.mousemovehandler, false);
    canvas!.addEventListener("mousedown", this.mousedownhandler, false);
    canvas!.addEventListener("mouseup", this.mouseuphandler, false);
    canvas!.addEventListener("mouseout", this.mouseuphandler, false);
  },

  mousemovehandler: (ev: MouseEvent) => {
    var offset = mouse.game!.canvas!.getBoundingClientRect();
    mouse.x = ev.clientX - offset.left;
    mouse.y = ev.clientY - offset.top;
    if (mouse.down) {
      mouse.dragging = true;
    }
    ev.preventDefault();
  },

  mousedownhandler: (ev) => {
    mouse.down = true;
    ev.preventDefault();
  },

  mouseuphandler: (ev) => {
    mouse.down = false;
    mouse.dragging = false;
    ev.preventDefault();
  },
};
